﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Security.Cryptography;
using System.IO;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Security;
using System.Xml;
using Org.BouncyCastle.Math;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Asn1.Pkcs;
using Org.BouncyCastle.Pkcs;
using Org.BouncyCastle.X509;
using Org.BouncyCastle.Asn1.X509;
using System.Text.RegularExpressions;

namespace Util
{
    /// <summary>
    /// RSA加解密帮助类
    /// 作者：代浩然
    /// 时间：2019-1-21 18:37
    /// </summary>
    public class RSAHelper
    {
        /// <summary>
        /// 生成公钥和私钥对
        /// </summary>
        public static void GeneratePublicAndPrivateKeyInfo()
        {
            RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
            using (StreamWriter writer = new StreamWriter("PrivateKey.xml"))  //这个文件要保密...
            {
                string privateKey = rsa.ToXmlString(true);
                writer.WriteLine(privateKey);
            }
            using (StreamWriter writer = new StreamWriter("PublicKey.xml"))
            {
                string publicKey = rsa.ToXmlString(false);
                writer.WriteLine(publicKey);
            }
        }

        /// <summary>
        /// 用私钥给数据进行RSA加密
        /// </summary>
        /// <param name="xmlPrivateKey"> 私钥(XML格式字符串)</param>
        /// <param name="strEncryptString">要加密的数据</param>
        /// <returns> 加密后的数据 </returns>
        public static string PrivateKeyEncrypt(string xmlPrivateKey, string strEncryptString)
        {
            //加载私钥
            RSACryptoServiceProvider privateRsa = new RSACryptoServiceProvider();
            privateRsa.FromXmlString((xmlPrivateKey));

            //转换密钥
            AsymmetricCipherKeyPair keyPair = DotNetUtilities.GetKeyPair(privateRsa);
            IBufferedCipher c = CipherUtilities.GetCipher("RSA/ECB/PKCS1Padding"); //使用RSA/ECB/PKCS1Padding格式
            //第一个参数为true表示加密，为false表示解密；第二个参数表示密钥

            c.Init(true, keyPair.Private);
            byte[] dataToEncrypt = Encoding.UTF8.GetBytes(strEncryptString);
            #region 分段加密
            int bufferSize = (privateRsa.KeySize / 8) - 11;
            byte[] buffer = new byte[bufferSize];
            byte[] outBytes = null;
            //分段加密
            using (MemoryStream input = new MemoryStream(dataToEncrypt))
            using (MemoryStream ouput = new MemoryStream())
            {
                while (true)
                {
                    int readLine = input.Read(buffer, 0, bufferSize);
                    if (readLine <= 0)
                    {
                        break;
                    }
                    byte[] temp = new byte[readLine];
                    Array.Copy(buffer, 0, temp, 0, readLine);
                    byte[] encrypt = c.DoFinal(temp);
                    ouput.Write(encrypt, 0, encrypt.Length);
                }
                outBytes = ouput.ToArray();
            }
            #endregion
            //byte[] outBytes = c.DoFinal(DataToEncrypt);//加密
            string strBase64 = Convert.ToBase64String(outBytes);

            return strBase64;
        }

        /// <summary>
        /// 用公钥给数据进行RSA解密 
        /// </summary>
        /// <param name="xmlPublicKey"> 公钥(XML格式字符串) </param>
        /// <param name="strDecryptString"> 要解密数据 </param>
        /// <returns> 解密后的数据 </returns>
        public static string PublicKeyDecrypt(string xmlPublicKey, string strDecryptString)
        {
            //string val = ReadFile(xmlPublicKey);
            ////if (val.IndexOf("-----BEGIN PUBLIC KEY-----") != -1)
            ////{
            ////    val = val.Replace("-----BEGIN PUBLIC KEY-----", ""); 
            ////    val = val.Replace("-----END PUBLIC KEY-----", "");
            ////    val = val.Replace("\n", "");
            ////    val.Trim();
            ////}
            //string key= RSAPublicKeyJava2DotNet(val);
            //加载公钥
            RSACryptoServiceProvider publicRsa = new RSACryptoServiceProvider();
            publicRsa.FromXmlString(xmlPublicKey);
            RSAParameters rp = publicRsa.ExportParameters(false);

            //转换密钥
            AsymmetricKeyParameter pbk = DotNetUtilities.GetRsaPublicKey(rp);

            IBufferedCipher c = CipherUtilities.GetCipher("RSA/ECB/PKCS1Padding");
            //第一个参数为true表示加密，为false表示解密；第二个参数表示密钥
            c.Init(false, pbk);
            byte[] outBytes = null;
            byte[] dataToDecrypt = Convert.FromBase64String(strDecryptString);
            #region 分段解密
            int keySize = publicRsa.KeySize / 8;
            byte[] buffer = new byte[keySize];

            using (MemoryStream input = new MemoryStream(dataToDecrypt))
            using (MemoryStream output = new MemoryStream())
            {
                while (true)
                {
                    int readLine = input.Read(buffer, 0, keySize);
                    if (readLine <= 0)
                    {
                        break;
                    }
                    byte[] temp = new byte[readLine];
                    Array.Copy(buffer, 0, temp, 0, readLine);
                    byte[] decrypt = c.DoFinal(temp);
                    output.Write(decrypt, 0, decrypt.Length);
                }
                outBytes = output.ToArray();
            }
            #endregion
            //byte[] outBytes = c.DoFinal(DataToDecrypt);//解密

            string strDec = Encoding.UTF8.GetString(outBytes);
            return strDec;
        }

        /// <summary>
        /// 使用公钥加密，分段加密
        /// </summary>
        /// <param name="publicKeyValue">密钥内容</param>
        /// <param name="strEncryptString">加密内容</param>
        /// <returns></returns>
        public static string EncrytByPublic(string publicKeyValue, string strEncryptString)
        {
            RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();

            //XML格式密钥
            rsa.FromXmlString(publicKeyValue);

            byte[] originalData = Encoding.UTF8.GetBytes(strEncryptString);
            if (originalData == null || originalData.Length <= 0)
            {
                throw new NotSupportedException();
            }
            if (rsa == null)
            {
                throw new ArgumentNullException();
            }
            byte[] encryContent = null;
            #region 分段加密
            int bufferSize = 117;
            byte[] buffer = new byte[bufferSize];
            //分段加密
            using (MemoryStream input = new MemoryStream(originalData))
            using (MemoryStream ouput = new MemoryStream())
            {
                while (true)
                {
                    int readLine = input.Read(buffer, 0, bufferSize);
                    if (readLine <= 0)
                    {
                        break;
                    }
                    byte[] temp = new byte[readLine];
                    Array.Copy(buffer, 0, temp, 0, readLine);
                    byte[] encrypt = rsa.Encrypt(temp, false);
                    ouput.Write(encrypt, 0, encrypt.Length);
                }
                encryContent = ouput.ToArray();
            }
            #endregion
            return Convert.ToBase64String(encryContent);
        }

        /// <summary>
        /// 通过私钥解密，分段解密
        /// </summary>
        /// <param name="content"></param>
        /// <param name="privateKeyPath"></param>
        /// <returns></returns>
        public static string DecryptByPrivate(string privateKeyPath, string strDecryptString)
        {
            RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
            rsa.FromXmlString((privateKeyPath));
            byte[] encryptData = Convert.FromBase64String(strDecryptString);
            //byte[] dencryContent = rsa.Decrypt(encryptData, false);
            byte[] dencryContent = null;
            #region 分段解密
            if (encryptData == null || encryptData.Length <= 0)
            {
                throw new NotSupportedException();
            }

            int keySize = rsa.KeySize / 8; 
            byte[] buffer = new byte[keySize];

            using (MemoryStream input = new MemoryStream(encryptData))
            using (MemoryStream output = new MemoryStream())
            {
                while (true)
                {
                    int readLine = input.Read(buffer, 0, keySize);
                    if (readLine <= 0)
                    {
                        break;
                    }
                    byte[] temp = new byte[readLine];
                    Array.Copy(buffer, 0, temp, 0, readLine);
                    byte[] decrypt = rsa.Decrypt(temp, false);
                    output.Write(decrypt, 0, decrypt.Length);
                }
                dencryContent = output.ToArray();
            }
            #endregion
            return Encoding.UTF8.GetString(dencryContent);
        }

        /// <summary>
        /// 读取文件
        /// </summary>
        /// <param name="filePath"></param>
        /// <returns></returns>
        public static string ReadFile(string filePath)
        {
            string content = "";
            if (File.Exists(filePath))
            {
                content = File.ReadAllText(filePath);
                byte[] mybyte = Encoding.UTF8.GetBytes(content);
                content = Encoding.UTF8.GetString(mybyte);
            }
            return content;
        }

        /// <summary>
        /// 将私钥转换成java所用的私钥字符串
        /// </summary>
        /// <param name="privateKeyPath">私钥文件路径</param>
        /// <returns></returns>
        public static string RSAPrivateKeyDotNet2Java(string privateKeyPath)
        {
            XmlDocument doc = new XmlDocument();
            doc.LoadXml(ReadFile(privateKeyPath));
            BigInteger m = new BigInteger(1, Convert.FromBase64String(doc.DocumentElement.GetElementsByTagName("Modulus")[0].InnerText));
            BigInteger exp = new BigInteger(1, Convert.FromBase64String(doc.DocumentElement.GetElementsByTagName("Exponent")[0].InnerText));
            BigInteger d = new BigInteger(1, Convert.FromBase64String(doc.DocumentElement.GetElementsByTagName("D")[0].InnerText));
            BigInteger p = new BigInteger(1, Convert.FromBase64String(doc.DocumentElement.GetElementsByTagName("P")[0].InnerText));
            BigInteger q = new BigInteger(1, Convert.FromBase64String(doc.DocumentElement.GetElementsByTagName("Q")[0].InnerText));
            BigInteger dp = new BigInteger(1, Convert.FromBase64String(doc.DocumentElement.GetElementsByTagName("DP")[0].InnerText));
            BigInteger dq = new BigInteger(1, Convert.FromBase64String(doc.DocumentElement.GetElementsByTagName("DQ")[0].InnerText));
            BigInteger qinv = new BigInteger(1, Convert.FromBase64String(doc.DocumentElement.GetElementsByTagName("InverseQ")[0].InnerText));

            RsaPrivateCrtKeyParameters privateKeyParam = new RsaPrivateCrtKeyParameters(m, exp, d, p, q, dp, dq, qinv);

            PrivateKeyInfo privateKeyInfo = PrivateKeyInfoFactory.CreatePrivateKeyInfo(privateKeyParam);
            byte[] serializedPrivateBytes = privateKeyInfo.ToAsn1Object().GetEncoded();
            return Convert.ToBase64String(serializedPrivateBytes);    
        }

        /// <summary>
        /// 将公钥转换成java所用的公钥字符串
        /// </summary>
        /// <param name="publicKeyPath">公钥路径</param>
        /// <returns></returns>
        public static string RSAPublicKeyDotNet2Java(string publicKeyPath)
        {
            XmlDocument doc = new XmlDocument();
            doc.LoadXml(ReadFile(publicKeyPath));
            BigInteger m = new BigInteger(1, Convert.FromBase64String(doc.DocumentElement.GetElementsByTagName("Modulus")[0].InnerText));
            BigInteger p = new BigInteger(1, Convert.FromBase64String(doc.DocumentElement.GetElementsByTagName("Exponent")[0].InnerText));
            RsaKeyParameters pub = new RsaKeyParameters(false, m, p);

            SubjectPublicKeyInfo publicKeyInfo = SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(pub);
            byte[] serializedPublicBytes = publicKeyInfo.ToAsn1Object().GetDerEncoded();


            using (StreamWriter writer = new StreamWriter("java.key"))
            {
             
                writer.WriteLine(Convert.ToString(serializedPublicBytes));
            }
            return System.Text.Encoding.UTF8.GetString(serializedPublicBytes);
        }

        /// <summary>
        /// RSA公钥格式转换，java->.net
        /// </summary>
        /// <param name="publicKey">java生成的公钥</param>
        /// <returns></returns>
        public static string RSAPublicKeyJava2DotNet(string publicKey)
        {
            RsaKeyParameters publicKeyParam = (RsaKeyParameters)PublicKeyFactory.CreateKey(Convert.FromBase64String(publicKey));
            return string.Format("<RSAKeyValue><Modulus>{0}</Modulus><Exponent>{1}</Exponent></RSAKeyValue>",
                Convert.ToBase64String(publicKeyParam.Modulus.ToByteArrayUnsigned()),
                Convert.ToBase64String(publicKeyParam.Exponent.ToByteArrayUnsigned()));
        }

        /// <summary>
        /// 格式化公钥/私钥
        /// </summary>
        /// <param name="key">生成的公钥/私钥</param>
        /// <param name="type">true:公钥 false:私钥</param>
        /// <returns>PEM格式的公钥/私钥</returns>
        private static string Format(string key, bool type)
        {
            string result = string.Empty;

            int length = key.Length / 64;
            for (int i = 0; i < length; i++)
            {
                int start = i * 64;
                result = result + key.Substring(start, 64) + "\r\n";
            }

            result = result + key.Substring(length * 64);
            if (type)
            {
                result = result.Insert(0, "-----BEGIN PUBLIC KEY-----\r\n");
                result += "\r\n-----END PUBLIC KEY-----";
            }
            else
            {
                result = result.Insert(0, "-----BEGIN PRIVATE KEY-----\r\n");
                result += "\r\n-----END PRIVATE KEY-----";
            }

            return result;
        }
        
        /// <summary>
        /// PEM格式的密钥转XML格式
        /// </summary>
        /// <param name="pemkey">pem格式的密钥</param>
        /// <param name="isprikey">true：私钥;false：公钥</param>
        /// <returns>xml格式密钥</returns>
        public static string PEMToXML_All(string pemkey, bool isprikey)
        {
            if (isprikey)
            {
                pemkey = pemkey.Replace("-----BEGIN PRIVATE KEY-----", "").Replace("-----END PRIVATE KEY-----", "");
            }
            else
            {
                pemkey = pemkey.Replace("-----BEGIN PUBLIC KEY-----", "").Replace("-----END PUBLIC KEY-----", "");
            }
            string rsaKey = string.Empty;
            object pemObject = null;
            RSAParameters rsaPara = new RSAParameters();
            using (StringReader sReader = new StringReader(pemkey))
            {
                var pemReader = new Org.BouncyCastle.OpenSsl.PemReader(sReader);
                pemObject = pemReader.ReadObject();
            }
            //RSA私钥
            if (isprikey)
            {
                RsaPrivateCrtKeyParameters key = (RsaPrivateCrtKeyParameters)PrivateKeyFactory.CreateKey(Convert.FromBase64String(pemkey));
                rsaPara = new RSAParameters
                {
                    Modulus = key.Modulus.ToByteArrayUnsigned(),
                    Exponent = key.PublicExponent.ToByteArrayUnsigned(),
                    D = key.Exponent.ToByteArrayUnsigned(),
                    P = key.P.ToByteArrayUnsigned(),
                    Q = key.Q.ToByteArrayUnsigned(),
                    DP = key.DP.ToByteArrayUnsigned(),
                    DQ = key.DQ.ToByteArrayUnsigned(),
                    InverseQ = key.QInv.ToByteArrayUnsigned(),
                };
            }
            //RSA公钥
            else
            {
                RsaKeyParameters key = (RsaKeyParameters)PublicKeyFactory.CreateKey(Convert.FromBase64String(pemkey));
                rsaPara.Modulus = key.Modulus.ToByteArrayUnsigned();
                rsaPara.Exponent = key.Exponent.ToByteArrayUnsigned();
            }
            RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
            rsa.ImportParameters(rsaPara);
            using (StringWriter sw = new StringWriter())
            {
                sw.Write(rsa.ToXmlString(isprikey ? true : false));
                rsaKey = sw.ToString();
            }
            return rsaKey;
        }
    }
}
